41. 数据结构之元组

本章概要

  • 学习材料:交易记录元组、切片边界和NamedTuple字段定义。
  • 本章任务:运行 lst-tuple-indexinglst-namedtuple,完成正负索引、切片和字段访问。
  • 完成后你将得到:切片结果、NamedTuple字段值、可哈希性检查和中国债券拓展应用记录。
  • 自我检查:检查切片不改原元组;含可变元素的元组不得作为字典键。
  • 拓展练习:把NamedTuple拓展应用到另一笔中国债券记录。

元组:外层槽位不可重绑的有序序列

  • 元组是 Python 的有序序列;不可变性作用于外层槽位
  • 创建后,外层槽位的引用与顺序不能重新绑定、增删
  • 若槽位引用列表或字典,其中的可变对象仍可能变化
  • 完整性与安全还依赖校验、访问控制、版本管理和复制/所有权纪律

为什么需要不可变的数据结构?

  • 并发边界:读取元组槽位通常不改状态,但复合操作与内部共享可变对象仍可能需要锁
  • 哈希能力:字典键必须可哈希;元组仅在每个成员(包括嵌套成员)均可哈希时可作键
  • 内存优化:Python解释器可以对元组进行特定优化
  • 语义清晰:不可变性明确传达外层槽位不应重绑的意图

元组在金融数据分析中的价值

  • 交易参数意图:外层槽位固定可减少误改,但复合参数仍需校验、权限和版本控制
  • 浅层完整性边界:元组不固定其内部列表、字典等可变对象
  • 多值返回:函数返回均值和标准差等多个相关值
  • 条件字典键:仅当日期、股票代码等每个成员及其嵌套成员均可哈希时,组合元组才能作键

反例mutable_inside = ([1],) 的内部列表仍可追加元素,且 hash(mutable_inside) 会抛出 TypeError

元组的五大核心特性

特性 说明
浅层不可变性 创建后外层槽位不可增删或重绑;引用的可变对象仍可变化
有序性 元素按插入顺序存储,可通过索引访问
异构性 可包含不同类型的元素(整数、字符串等)
实现相关的大小 同成员引用下 CPython 元组通常较小;仍应在目标环境测量
可迭代性 可用于for循环、推导式等

异构性示例:一条股票Tick数据

tick_data = ('600519.SH', '2024-01-15 09:30:00', 1856.00, 100)
# 分别代表:股票代码、时间戳、价格、成交量
  • 一个元组可以包含不同类型的元素
  • 特别适合表示”记录”或”结构体”
  • 元素的位置赋予了元素语义

创建元组——方法1:使用圆括号

Listing 1: 元组的多种创建方式
展开代码
# 方式1: 使用圆括号创建元组
# 圆括号是可选的,但加上可以提高代码可读性
tup1 = ('physics', 'chemistry', 1997, 2000)  # 包含字符串和整数的混合元组
tup2 = (1, 2, 3, 4, 5)  # 纯整数元组
tup3 = "a", "b", "c", "d"  # 不加括号也可以,Python会自动识别为元组

# 创建空元组
# 空元组常用于初始化变量或作为函数默认参数
empty_tup = ()

# 创建单元素元组(注意逗号!)
# 这是一个常见的陷阱:单个元素后面必须有逗号,否则会被识别为带括号的表达式
single_tup = (50,)  # 正确:有逗号,是元组
not_a_tuple = (50)  # 错误:没有逗号,这只是整数50

# 打印结果以验证类型
print('元组1:', tup1)
print('元组2:', tup2)
print('元组3:', tup3)  # 证明不加括号也能创建元组
print('空元组:', empty_tup)
print('单元素元组:', single_tup)
元组1: ('physics', 'chemistry', 1997, 2000)
元组2: (1, 2, 3, 4, 5)
元组3: ('a', 'b', 'c', 'd')
空元组: ()
单元素元组: (50,)

注意:单元素元组的”逗号陷阱”

  • (50,) → 这是一个元组(有逗号)
  • (50) → 这只是整数50(没有逗号)
  • 单元素后面的逗号不能省略
single_tup = (50,)   # ✅ 正确:元组
not_a_tuple = (50)    # ❌ 错误:只是整数50

创建元组——方法2:tuple()构造函数

Listing 2: 使用tuple()构造函数
展开代码
# 从列表创建元组
list_data = [1, 2, 3, 4]
tup_from_list = tuple(list_data)

# 从字符串创建元组(每个字符成为一个元素)
tup_from_string = tuple('Hello')

# 从range对象创建元组
tup_from_range = tuple(range(5))

print('从列表:', tup_from_list)
print('从字符串:', tup_from_string)
print('从range:', tup_from_range)
从列表: (1, 2, 3, 4)
从字符串: ('H', 'e', 'l', 'l', 'o')
从range: (0, 1, 2, 3, 4)

创建元组——方法3:生成器表达式

Listing 3: 创建元组的推导式写法
展开代码
# 使用生成器表达式创建元组
# 生成器表达式用圆括号表示,然后传给tuple()函数
squares = tuple(x**2 for x in range(6))

# 这等价于先创建列表再转换,但更节省内存
# 因为生成器是惰性求值的,不需要创建中间列表

print('平方数元组:', squares)
平方数元组: (0, 1, 4, 9, 16, 25)

创建方法选择指南

场景 推荐方法 理由
已知元素,数量少 tup = (1, 2) 简洁直观
从其他序列转换 tuple(seq) 清晰表达意图
需要计算生成元素 tuple(x for x in ...) 内存高效
需要空元组 empty = () 最简洁

元组的索引:正向与负向

Listing 4: 元组索引与切片操作
展开代码
# 创建示例元组
tup1 = ('physics', 'chemistry', 1997, 2000)  # 混合类型元组
tup2 = (1, 2, 3, 4, 5, 6, 7)  # 纯数字元组

# 正向索引:从0开始,从左向右数
print('tup1[0]:', tup1[0])  # 第一个元素:'physics'
print('tup1[1]:', tup1[1])  # 第二个元素:'chemistry'

# 负向索引:从-1开始,从右向左数
print('tup2[-1]:', tup2[-1])  # 最后一个元素:7
print('tup2[-2]:', tup2[-2])  # 倒数第二个元素:6

# 切片操作:获取子元组
# 语法:tup[start:end:step]
# start包含,end不包含(左闭右开区间)
print('tup2[1:5]:', tup2[1:5])  # 索引1到4的元素:(2, 3, 4, 5)
print('tup2[::2]:', tup2[::2])  # 每隔一个取一个:(1, 3, 5, 7)
print('tup2[::-1]:', tup2[::-1])  # 反转元组:(7, 6, 5, 4, 3, 2, 1)
tup1[0]: physics
tup1[1]: chemistry
tup2[-1]: 7
tup2[-2]: 6
tup2[1:5]: (2, 3, 4, 5)
tup2[::2]: (1, 3, 5, 7)
tup2[::-1]: (7, 6, 5, 4, 3, 2, 1)

切片操作详解

语法 含义 示例
[start:end] 从start到end-1 tup[1:5]
[start:end:step] 按步长取元素 tup[::2]
[:end] 从头开始 tup[:3]
[start:] 到结尾 tup[2:]
[::-1] 反转序列 tup[::-1]

索引越界会报错

Listing 5: 元组索引越界演示
展开代码
tup = (1, 2, 3)

# 下面的代码会抛出IndexError
try:
    print(tup[10])  # 索引10不存在
except IndexError as e:
    print(f'错误:索引超出范围 - {e}')
错误:索引超出范围 - tuple index out of range
  • 元组长度固定,访问不存在的索引会引发 IndexError
  • 可使用 try/except 或先检查 len() 来避免

不可变性的含义

  • ❌ 不能重绑已有外层槽位
  • ❌ 不能删除外层槽位
  • ❌ 不能添加新外层槽位
  • ✅ 可以重新赋值变量为新的元组
  • ✅ 可以连接两个元组创建新元组
  • ⚠️ 槽位引用列表或字典时,内部对象仍可修改

不可变性演示:修改会报错

Listing 6: 元组不可变性演示
展开代码
# 创建一个元组
tup = (12, 34.56)

# 尝试修改元组的第一个元素
try:
    tup[0] = 20  # 这会抛出TypeError
except TypeError as e:
    print(f'错误类型: {type(e).__name__}')
    print(f'错误信息: {e}')
    print('结论:元组外层槽位一旦创建就不能重绑')  # 该报错不证明内部可变对象被固定

# 虽然不能修改,但可以连接两个元组创建新元组
# 注意:这不是修改原元组,而是创建了一个全新的元组
tup3 = tup + ('abc', 'xyz')
print('原元组:', tup)  # 原元组保持不变
print('新元组:', tup3)  # 这是一个新创建的元组

# 也可以通过重复创建新元组
tup4 = tup * 2  # 元组重复
print('重复元组:', tup4)
错误类型: TypeError
错误信息: 'tuple' object does not support item assignment
结论:元组外层槽位一旦创建就不能重绑
原元组: (12, 34.56)
新元组: (12, 34.56, 'abc', 'xyz')
重复元组: (12, 34.56, 12, 34.56)

为什么需要不可变性?

理由 说明
误改边界 只固定外层槽位;完整性另靠校验、授权、版本和所有权
多线程环境 槽位不可变不保证复合操作线程安全;共享状态按协议加锁
哈希要求 键必须可哈希;含列表等不可哈希元素的元组不能作键
函数式编程 使程序更易于推理和测试
内存优化 Python可对相同内容的元组共享内存

应用1:交易参数配置

Listing 7: 元组在金融交易参数配置中的应用
展开代码
# 定义交易策略的核心参数
# 使用元组避免外层参数被意外改动;实际设置仍需检查、授权并记录版本
trading_params = (
    100000,    # 初始资金(元)
    0.05,       # 预期年收益率
    30,         # 投资期限(年)
    0.6         # 股票配置比例
)

# 元组解包:将元组的元素分别赋值给变量
# 这种写法比分别访问元组元素更清晰
initial_capital, rate, years, stock_ratio = trading_params

# 格式化输出参数
# 使用f-string进行格式化,提高可读性
print(f'初始资金: {initial_capital:,.0f}元')  # 添加千位分隔符
print(f'年利率: {rate:.1%}')  # 百分比格式
print(f'投资期限: {years}年')
print(f'股票配置: {stock_ratio:.0%}')

# 计算期末资产
final_amount = initial_capital * (1 + rate) ** years
print(f'期末资产(复利): {final_amount:,.2f}元')
初始资金: 100,000元
年利率: 5.0%
投资期限: 30年
股票配置: 60%
期末资产(复利): 432,194.24元

应用2:元组作为字典键

Listing 8: 使用元组作为字典键
展开代码
# 创建价格查找表
# 键是(股票代码, 日期)的元组,值是收盘价
price_lookup = {
    ('600519.SH', '2024-01-15'): 1856.00,
    ('600519.SH', '2024-01-16'): 1862.50,
    ('000858.SZ', '2024-01-15'): 158.20,
    ('000858.SZ', '2024-01-16'): 159.80
}

# 查询特定股票在特定日期的价格
stock = '600519.SH'
date = '2024-01-15'
price = price_lookup.get((stock, date))

if price:
    print(f'{stock}{date} 的收盘价是 {price:.2f}元')
else:
    print('未找到匹配的价格数据')

# 字典键必须可哈希;元组仅在每个成员及其嵌套成员均可哈希时才能作为键
# 下面的代码会报错,因为列表是可变的:
# error_dict = {['600519.SH', '2024-01-15']: 1856.00}  # TypeError
600519.SH 在 2024-01-15 的收盘价是 1856.00元

应用3:函数返回多个值

Listing 9: 函数返回多个值使用元组
展开代码
def calculate_return_metrics(prices):
    '''
    计算价格序列的多个收益率指标

    参数:
        prices: 价格列表

    返回:
        (总收益率, 年化收益率, 波动率)的元组
    '''
    import numpy as np

    # 计算简单收益率
    returns = np.diff(prices) / prices[:-1]

    # 总收益率
    total_return = (prices[-1] - prices[0]) / prices[0]

    # 年化收益率(假设252个交易日)
    annual_return = (1 + total_return) ** (252 / len(prices)) - 1

    # 年化波动率
    annual_volatility = returns.std() * np.sqrt(252)

    return total_return, annual_return, annual_volatility

# 使用示例
stock_prices = [100, 102, 101, 105, 108, 107, 110]
total_ret, ann_ret, ann_vol = calculate_return_metrics(stock_prices)

print(f'总收益率: {total_ret:.2%}')
print(f'年化收益率: {ann_ret:.2%}')
print(f'年化波动率: {ann_vol:.2%}')
总收益率: 10.00%
年化收益率: 2991.27%
年化波动率: 30.26%

应用4:时间序列数据记录

Listing 10: 使用元组表示时间序列数据点
展开代码
# 定义一个时间戳数据点
tick = ('600519.SH', '2024-01-15 09:30:00', 1856.00, 100)
# 格式:(股票代码, 时间戳, 价格, 成交量)

# 元组解包提取字段
symbol, timestamp, price, volume = tick

print(f'股票代码: {symbol}')
print(f'时间戳: {timestamp}')
print(f'价格: {price:.2f}元')
print(f'成交量: {volume}手')

# 将多个tick数据组成列表
ticks = [
    ('600519.SH', '09:30:00', 1856.00, 100),
    ('600519.SH', '09:31:00', 1858.50, 150),
    ('600519.SH', '09:32:00', 1855.00, 80),
]

# 分析第一分钟的价格变化
price_change = ticks[1][2] - ticks[0][2]
print(f'第一分钟价格变化: {price_change:+.2f}元')
股票代码: 600519.SH
时间戳: 2024-01-15 09:30:00
价格: 1856.00元
成交量: 100手
第一分钟价格变化: +2.50元

元组 vs 列表:如何选择?

选择元组 选择列表
外层槽位需固定,内层对象另行管理 数据会频繁增删
所有成员及嵌套成员均可哈希且需要复合键 逐步构建数据序列
在目标环境实测后满足大小/时间约束 在目标环境实测后更符合实际负载
函数返回多个值 需要排序、追加等操作
固定的记录结构 动态变化的集合

性能探针:大小与访问时间依赖环境

Listing 11: 元组与列表的性能对比
展开代码
import sys
import timeit

# 创建相同内容的元组和列表
tup = tuple(range(1000))
lst = list(range(1000))

# 内存占用比较
print(f'元组内存占用: {sys.getsizeof(tup)} 字节')
print(f'列表内存占用: {sys.getsizeof(lst)} 字节')
print(f'元组更节省: {sys.getsizeof(lst) - sys.getsizeof(tup)} 字节')

# 在同一进程做访问时间微基准;结果只代表本次环境与负载
tup_time = timeit.timeit('x = tup[500]', globals=globals(), number=1000000)
lst_time = timeit.timeit('x = lst[500]', globals=globals(), number=1000000)

print(f'元组访问时间: {tup_time:.4f}秒')
print(f'列表访问时间: {lst_time:.4f}秒')
print(f'本次元组相对列表时间差: {(lst_time/tup_time - 1)*100:.1f}%')  # 正值表示本次元组更快,负值表示更慢
元组内存占用: 8040 字节
列表内存占用: 8056 字节
元组更节省: 16 字节
元组访问时间: 0.0251秒
列表访问时间: 0.0261秒
本次元组相对列表时间差: 3.9%
  • 基线观察:写出 59823756 的一次本机运行得到元组 0.0292s、列表 0.0240s、相对差 -17.5%;该次结果不支持“元组访问更快”。
  • 决策原则:微基准会受 Python 版本、硬件、预热、重复次数和实际负载影响;应在目标环境重复测量,并结合语义、大小与可变性需求选择结构。

命名元组:带字段名的增强版元组

Listing 12: 命名元组的使用
展开代码
from collections import namedtuple

# 定义一个命名元组类型
# 相当于创建了一个简单的类
Stock = namedtuple('Stock', ['symbol', 'price', 'volume'])

# 创建Stock实例
stock1 = Stock('600519.SH', 1856.00, 1000)
stock2 = Stock(symbol='000858.SZ', price=158.20, volume=5000)

# 可以像元组一样索引访问
print(f'股票代码(索引): {stock1[0]}')

# 也可以像对象一样属性访问
print(f'股票代码(属性): {stock1.symbol}')
print(f'价格: {stock1.price}')
print(f'成交量: {stock1.volume}')

# 命名元组仍是元组,只保证外层槽位不可重绑
print(f'是否为元组: {isinstance(stock1, tuple)}')

# 命名元组有友好的字符串表示
print(f'股票信息: {stock1}')
股票代码(索引): 600519.SH
股票代码(属性): 600519.SH
价格: 1856.0
成交量: 1000
是否为元组: True
股票信息: Stock(symbol='600519.SH', price=1856.0, volume=1000)

总结:元组核心要点

  • 浅层不可变性——外层槽位不可重绑,内部可变对象仍可变化
  • 完整性与安全是系统属性——还需校验、授权、版本和所有权纪律
  • 善用元组解包——使代码更清晰、更Pythonic
  • 条件可哈希——只有每个成员及其嵌套成员均可哈希时,元组才能作字典键
  • 性能按负载实测——单次微基准不能证明通用速度优势

运行前预测|平台任务1解答代码

  • 输入预测:运行前先写出 namecodeIPO_dateexchange 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到“name:”相关结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务1解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务1解答代码

展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
name = "中国通号"  # 设置名称为"中国通号"
print('name:',type(name))  # 输出name
 
code = "688009"  # 设置代码为"688009"
print('code:',type(code))  # 输出code
 
IPO_date = "2019年7月22日"  # 设置日期为"2019年7月22日"
print('IPO_data:',type(IPO_date))  # 输出IPO_data
 
exchange = "上海证券交易所"  #以字符串输入中国通号A股的上市交易所
print('exchange',type(exchange))               #使用type函数输出数据类型
 
capital = 10589819000  # 总股本:10589819000股
print('capital',type(capital))  # 输出capital
 
shares = 8621018000  # 流通股本:8621018000股
print('share:',type(shares))  # 输出share
 
price_IPO = 5.85          #以浮点型输入中国通号A股发行价
print('price_IPO:',type(price_IPO))    #使用type函数输出数据类型并使用print函数打印
 
price_Jul22 = 12.27  # 设置当前价格为12.27
print('price_Jul22:',type(price_Jul22))  # 输出price_Jul22
 
change_Jul22 = 1.09744  # 涨幅:109.744%
print('change_Jul22:',type(change_Jul22))  # 输出change_Jul22

任务复盘|平台任务1解答代码

运行后核对:核对 namecodeIPO_dateexchange 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

运行前预测|平台任务2解答代码

  • 输入预测:运行前先写出 namecodeIPO_dateexchange 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到tup 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务2解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务2解答代码

展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
name = "中国通号"  # 设置名称为"中国通号"
code = "688009"  # 设置代码为"688009"
IPO_date = "2019年7月22日"  # 设置日期为"2019年7月22日"
exchange = "上海证券交易所"  # 设置交易所为"上海证券交易所"
capital = 10589819000  # 总股本:10589819000股
shares = 8621018000  # 流通股本:8621018000股
price_IPO = 5.85  # 设置当前价格为5.85
price_Jul22 = 12.27  # 设置当前价格为12.27
change_Jul22 = 1.09744  # 涨幅:109.744%
 
tup =(name,code,IPO_date,exchange,capital,shares,price_IPO,price_Jul22,change_Jul22)                  #创建一个元组,包括以上数据
print(tup)                                     #打印输出这个元组
    
print(tup[0])                                             #访问该元组的首个元素
print(tup[-1])                                             #访问该元组的末尾元素
print(tup[2:6])#访问该元组的第3个至第6个元素

任务复盘|平台任务2解答代码

运行后核对:核对 namecodeIPO_dateexchange 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

运行前预测|平台任务3解答代码

  • 输入预测:运行前先写出 namecodeIPO_dateexchange 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到tup_new 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“平台任务3解答代码”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 平台任务3解答代码

展开完整代码(投影默认折叠)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
name = "中国通号"  # 设置名称为"中国通号"
code = "688009"  # 设置代码为"688009"
IPO_date = "2019年7月22日"  # 设置日期为"2019年7月22日"
exchange = "上海证券交易所"  # 设置交易所为"上海证券交易所"
capital = 10589819000  # 总股本:10589819000股
shares = 8621018000  # 流通股本:8621018000股
price_IPO = 5.85  # 设置当前价格为5.85
price_Jul22 = 12.27  # 设置当前价格为12.27
change_Jul22 = 1.09744  # 涨幅:109.744%
tup = (name,code,IPO_date,exchange,capital,shares,price_IPO,price_Jul22,change_Jul22)  # 定义元组tup
 
price_Jul22_H = 5.43     #以浮点型输入中国通号H股7月22日收盘价
change_Jul22_H = -0.1171 #以浮点数输入中国通号H股7月22日涨跌幅
del tup                  #删除任务2中创建的元组
tup_new = (name,code,IPO_date,exchange,capital,shares,price_IPO,price_Jul22,change_Jul22,price_Jul22_H,change_Jul22_H)   #创建包含H股股价和涨跌幅的新元组
print(tup_new)  #打印这个新元组

任务复盘|平台任务3解答代码

运行后核对:核对 namecodeIPO_dateexchange 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

随堂练习

  • 问题 1|需要准备哪些数据?:交易记录元组、切片边界和NamedTuple字段定义。
  • 问题 2|需要完成哪些操作?:运行 lst-tuple-indexinglst-namedtuple,完成正负索引、切片和字段访问。
  • 问题 3|应得到哪些结果?:切片结果、NamedTuple字段值、稳定诊断 HASH_STOP:TypeError 和中国债券拓展应用记录。
  • 问题 4|怎样确认结果可靠?:检查切片不改原元组;实际执行 hash(mutable_inside),确认程序能够捕获 TypeError
  • 问题 5|换一个情境,怎样继续应用?:把NamedTuple拓展应用到另一笔中国债券记录。
  • 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。

教师参考解答|答案与说明 1

  • 所用数据与字段:公司记录 name, code, ipo_date, exchange;拓展应用记录 bond_name, bond_code, issuer, maturity_date, coupon_rate;两者字段顺序固定。

教师参考解答|代码 1

展开代码(代码区可独立滚动)
from typing import NamedTuple  # 导入命名元组以建立可按字段访问的固定记录
class Listing(NamedTuple):  # 导入命名元组以建立可按字段访问的固定记录
    name:str; code:str; ipo_date:str; exchange:str  # 执行当前步骤并保留可追踪结果
listing_record=Listing('中国通号','688009.SH','2019-07-22','上海证券交易所')  # 构造带字段名的上市记录元组
assert listing_record[:2]==('中国通号','688009.SH') and listing_record.code=='688009.SH'  # 构造带字段名的上市记录元组
hashable_key=(listing_record.code,listing_record.ipo_date); company_mapping={hashable_key:listing_record.name}  # 构造带字段名的上市记录元组
class BondRecord(NamedTuple):  # 定义可按字段访问的中国债券新案例结构
    bond_name:str; bond_code:str; issuer:str; maturity_date:str; coupon_rate:float  # 固定债券识别与条款字段顺序
bond_record=BondRecord('24国债02','019733','中华人民共和国财政部','2025-01-25',1.96)  # 依据上交所通知构造另一笔中国国债记录
bond_slice=bond_record[:2]  # 用切片提取债券简称和代码
assert bond_slice==('24国债02','019733') and bond_record.coupon_rate==1.96  # 核对债券新案例的切片与字段访问
mutable_inside=([],)  # 构造含列表元组以说明不可哈希边界
hash_diagnostic='HASH_CHECK_FAILED'  # 预设失败状态以防异常检查被跳过
try:  # 实际执行含可变成员元组的哈希检查
    hash(mutable_inside)  # 满足条件列表成员不可哈希的边界
except TypeError:  # 只捕获教学预期的不可哈希异常
    hash_diagnostic='HASH_STOP:TypeError'  # 生成跨环境稳定的停止依据
if hash_diagnostic!='HASH_STOP:TypeError':  # 防止检查意外通过却继续写出结果
    raise AssertionError('停止:含可变成员的元组未满足条件TypeError')  # 显式暂停不符合边界的结果
print(listing_record,listing_record[-1],company_mapping,hash_diagnostic)  # 输出字段、映射与哈希停止依据
print(bond_record,bond_record.maturity_date,bond_slice)  # 输出债券新案例记录、字段访问与切片依据

教师参考解答|答案与说明 2

  • 解释答案:元组只保证外层绑定不可增删改;若内部含列表,整体仍不可哈希,因此能否作字典键必须由所有成员的可哈希性决定。
  • 拓展应用答案:把上市记录拓展应用为 BondRecord,用字段固定债券简称、代码、发行人、到期日与票面利率;第二条实例为中国“24国债02”,并以字段访问和切片核对。条款依据上海证券交易所上市通知
  • 参考结果:命名上市记录、可哈希键映射、稳定诊断 HASH_STOP:TypeError,以及债券记录、到期日字段和债券简称/代码切片。另行核对:代码必须实际执行 hash(mutable_inside) 并捕获 TypeError;债券字段与切片检查也必须通过。
  • 常见错误:单元素元组漏逗号;认为所有元组可作键;只在文字中声明异常而未执行;写了拓展应用说明却没有第二条债券实例。